PHP 的魔术方法

PHP 魔术方法

魔术方法

PHP中,将所有以__(两个下划线)开头的类方法保留位魔术方法,所以在定义类方法时,不建议使用__作为方法的前缀。下面分别介绍每个魔术方法的作用。

__get __set __isset __unset方法

__get __set __isset __unset这些魔术方法为在类和他们的父类中没有声明的属性而设计的

  1. 在访问类属性的时候,如果属性可以访问,则直接返回;若不可以访问,则调用__get函数

    方法签名:

    1
    public mixed __get(string $name)
  1. 在设置一个对象的属性时,若对象可以访问,则直接赋值,若不可以访问,则调用__set方法

    方法签名:

    1
    public void __set(string $name , mixed $value)
  2. 当对不可访问的属性调用isset()empty()时,__isset()会被调用

    方法签名:

    1
    public bool __isset(string $name)
  3. 当对不可访问的属性调用unset()时,__unset()会被调用

    方法签名:

    1
    public bool __unset(string $name)

注意:

以上存在的不可访问不仅仅是属性没有定义,当属性的访问控制为protecedprivate时,也属于不可访问的情况

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Demo{
/* 保存未定义的变量对象 */
private $data = array();
/* 访问不可访问的属性时调用 */
public function __get($name){
if (array_key_exists($name,$this->data))
return $this -> data[$name];
return NULL;
}

/* 设置不可访问的属性时调用 */
public function __set($name,$value){
$this -> data[$name] = $value;
}

/* 对不可访问的属性调用isset或empty时调用 */
public function __isset($name){
return isset($this -> data[$name]);
}

/* 对不可访问的属性调用unset时调用 */
public function __unset($name){
unset($this -> data[$name]);
}

/* 打印data属性的内容 */
public function getData(){
echo "<pre>";
var_dump($this -> data);
echo "<pre>";
}
}
$obj = new Demo;
// 设置不存在的属性时
$obj -> a = "wertycn";
echo $obj -> a ;
$obj -> getData();

// 设置无访问权限的属性时
$obj -> data = "WERTY";
$obj -> getData();

// 对无访问权限的属性做isset时
isset($obj -> data);
$obj -> getData();

// 销毁无访问权限的属性时
unset($obj -> data);
$obj -> getData();

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
wertycn

array(1) {
["a"]=>
string(7) "wertycn"
}

array(2) {
["a"]=>
string(7) "wertycn"
["data"]=>
string(5) "WERTY"
}

array(2) {
["a"]=>
string(7) "wertycn"
["data"]=>
string(5) "WERTY"
}

array(1) {
["a"]=>
string(7) "wertycn"
}

__construct __destruct 方法

  1. __construct()构造函数,实例化对象时被调用
  2. __destruct()析构函数,当对象被销毁时被调用。通常情况下,PHP只会释放对象所占有的内存和相关的资源,对于程序员自己申请的资源,需要显式的去释放。通常可以把需要释放资源的操作放到析构方法中,这样可以保证在对象被释放的时候,自己申请的资源也能被释放。

实例:

在构造函数中打开一个文件,然后再析构函数中关闭文件

1
2
3
4
5
6
7
8
9
class Demo{
protected $file = NULL;
public function __construct($filepath){
$this -> file = fopen($filepath,"r");
}
public function __destruct(){
fcolse($this -> file);
}
}

也可以用在创建数据库连接和销毁数据库连接中等等

__call()方法和__callStatic()

  1. __call($method,$arg_array): 当调用一个不可访问的方法时会自动调用这个方法

    方法签名:

    1
    public void function __call(string $name,array $arguments)
  2. __callStatic()__call方法类似,当调用的静态方法不存在时,会自动调用这个方法

    1
    public void function __callStatic(string $name,array $arguments)

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Demo{
public function __call($name,$arguments){
echo '调用了一个不可访问的的对象方法'.$name;
echo '<br>';
echo '参数列表:['.implode(',',$arguments).']';
echo '<br>';

}

public static function __callStatic($name,$arguments){
echo '调用了一个不可访问的的静态对象方法'.$name;
echo '<br>';
echo '参数列表:['.implode(',',$arguments).']';
echo '<br>';
}
}
// 静态调用不存在的方法 自动执行 __callStatic
Demo::get("wertycn","werty","debug.icu");

// 实例化调用不存在的方法 自动执行 __callStatic
$demo = new Demo;
$demo->get("werty","debug.icu");

执行结果:

1
2
3
4
调用了一个不可访问的的静态对象方法get
参数列表:[wertycn,werty,debug.icu]
调用了一个不可访问的的对象方法get
参数列表:[werty,debug.icu]

拓展:

__call方法在设计流接口模式(链式调用)时经常用到,如ThinkPHP5的Db类,就使用了__callStatic方法,调用查询构建器中的方法,配合call_user_func_array方法和return $this实现,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
echo "<pre>";

class Test1{
public function get(){
echo "我是Test1类中的get方法<br>";
return $this;
}
private function getTest2Obj(){
$test2 = new Test2;
return $test2;
}
public function __call($name,$arguments){
echo "我是Test1中的__call方法,您调用的{$name}方法在Test1中没有找到,我将尝试在Test2中寻找<br>";

return call_user_func_array([$this -> getTest2Obj(), $name], $arguments);

}
}

class Test2{
public function set(){
echo "我是Test2类中的set方法<br>";
return $this;
}

public function __call($name,$arguments){
echo "我是Test2中的__call方法,您调用的{$name}方法没有找到<br>";
}
}

class Demo{
public function __call($name,$arguments){
echo '调用了一个不可访问的的对象方法'.$name;
echo '<br>';
echo '参数列表:['.implode(',',$arguments).']';
echo '<br>';
}

public static function __callStatic($name,$arguments){
echo "我是Demo中的__callStatic方法,您调用的{$name}方法在Demo中不能访问<br>";
$test1 = new Test1;
return call_user_func_array([$test1, $name], $arguments);

}
}
// 静态调用不存在的方法 自动执行 __callStatic
// Demo::get("wertycn","werty","debug.icu");
echo "===========================<br>";
Demo::get("wertycn","werty","debug.icu");
echo "<br>===========================<br>";
Demo::set();
echo "<br>===========================<br>";
Demo::get()->set()->work();

注意 : 在使用call_user_func_array()时注意return

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
===========================

我是Demo中的__callStatic方法,您调用的get方法在Demo中不能访问
我是Test1类中的get方法


===========================

我是Demo中的__callStatic方法,您调用的set方法在Demo中不能访问
我是Test1中的__call方法,您调用的set方法在Test1中没有找到,我将尝试在Test2中寻找
我是Test2类中的set方法


===========================

我是Demo中的__callStatic方法,您调用的get方法在Demo中不能访问
我是Test1类中的get方法
我是Test1中的__call方法,您调用的set方法在Test1中没有找到,我将尝试在Test2中寻找
我是Test2类中的set方法
我是Test2中的__call方法,您调用的work方法没有找到

__sleep()和__wakeup()方法

  1. __sleep()串行化的时候调用
  2. __wakeup()反串行化的时候调用
  • 在执行serialize()unserialize()时,会优先调用这两个函数

  • 串行化用于对对象的存储或者传输,通过反串行化得到这个对象。通过串行化,可以将数据对象转换成二进制数据格式